# Weather - Weather library for Entertainer media center
# Copyright (C) 2008 Joshua Scotton <josh@joshuascotton.com>
# 
# Entertainer is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# Entertainer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2008, Joshua Scotton"
__author__ = "Joshua Scotton <josh@joshuascotton.com>"

import urllib
import httplib

from xml.dom import minidom 
from feed_utils import FeedEntryParser

#The lookup url for the rss feed
WEATHER_LOOKUP_URL = "http://xml.weather.yahoo.com/forecastrss"

#This is a dict of the weather codes from yahoo.com
WEATHER_CODES = {   0:"tornado",
                    1:"tropical storm",
                    2:"hurricane",
                    3:"severe thunderstorms",
                    4:"thunderstorms",
                    5:"mixed rain and snow",
                    6:"mixed rain and sleet",
                    7:"mixed snow and sleet",
                    8:"freezing drizzle",
                    9:"drizzle",
                    10:"freezing rain",
                    11:"showers",
                    12:"showers",
                    13:"snow flurries",
                    14:"light snow showers",
                    15:"blowing snow",
                    16:"snow",
                    17:"hail",
                    18:"sleet",
                    19:"dust",
                    20:"foggy",
                    21:"haze",
                    22:"smoky",
                    23:"blustery",
                    24:"windy",
                    25:"cold",
                    26:"cloudy",
                    27:"mostly cloudy (night)",
                    28:"mostly cloudy (day)",
                    29:"partly cloudy (night)",
                    30:"partly cloudy (day)",
                    31:"clear (night)",
                    32:"sunny",
                    33:"fair (night)",
                    34:"fair (day)",
                    35:"mixed rain and hail",
                    36:"hot",
                    37:"isolated thunderstorms",
                    38:"scattered thunderstorms",
                    39:"scattered thunderstorms",
                    40:"scattered showers",
                    41:"heavy snow",
                    42:"scattered snow showers",
                    43:"heavy snow",
                    44:"partly cloudy",
                    45:"thundershowers",
                    46:"snow showers",
                    47:"isolated thundershowers",
                    3200:"not available"
                    }

def get_page(type, url, params = {}, headers = {}):
    """
    Gets a HTTPConnection for the passed arguments
    @param type String (GET or POST)
    @param url String
    @param params dict(String:String)
    @param headers dict(String:String)
    @return HTTPConnection
    """
    httpServ = httplib.HTTPConnection("weather.yahoo.com",80)
    httpServ.connect()

    httpServ.request(type, url, params, headers)
    
    return httpServ.getresponse()
    

class Weather:
    """
    Weather class
    @author: Joshua Scotton <josh@joshuascotton.com>
    """
    
    def __init__(self, locID="", type="f"):
        """
        Sets up the weather object
        @param locID (String) The location ID for the Yahoo Weather Service
        @param type (String) temperature unit of measurement (f or c)
        """
        self.set_location_id(locID)
        self.set_type(type)
        self.refresh()
        
    def find_weather_search_results_page(self, search):
        """
        Returns the search results page for the search
        @param search String
        @return HTTPConnection
        """
        headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
        params = urllib.urlencode({"ptrigger2":search, "psubmit2":"Go"})

        return get_page('POST', '/search/weather2', params, headers)

    def search(self, search_string):
        """
        Searchs Yahoo for location in search_string and returns list of tuples
        @param search_string String
        @return list(tuple(location_id String, PlaceName String))
        """
        response = self.find_weather_search_results_page(search_string)
    
        if response.status == 302:
            redirect_url = response.getheader('location', '')
            the_code = redirect_url[10:len(redirect_url)-5]
            
            the_placename = self.get_location_from_id(the_code)
            
            return [(the_code, the_placename)]
        else:
            return []
            
    def get_location_from_id(self, locID, url="http://weather.yahoo.com/forecast/"):
        redirect_page = get_page('GET', url+locID+".html").read()
        start = redirect_page.find("<a href=\"/\">Weather</a>")
        end = redirect_page.find("<div id=\"degree-switch\">")
        snippet = redirect_page[start:end][26:]
        snips = snippet.split("\n")
        snippet = ""
        for snip in snips:
            snippet += " " + snip.strip()
        return FeedEntryParser().strip_tags(snippet).strip()

    def set_location_id(self, locID):
        """
        Sets the location id
        @param locID String
        """
        self.__locID = locID
        
    def get_location_id(self):
        """
        Returns the location id
        @return String
        """
        return self.__locID
    
    def set_type(self, type):
        """
        Sets the temperature unit type
        @param type String should be f or c, if not it will default to c
        """
        if (type == "f") or (type == "F"):
            self.__type = "f"
        else:
            self.__type = "c"
            
    def get_type(self):
        """
        Returns the temperature unit type
        @return String
        """
        return self.__type
        
    def get_rss_url(self):
        """
        Returns the complete rss url
        @return String
        """
        return WEATHER_LOOKUP_URL + "?p=" + self.get_location_id() + "&u=" + self.get_type()
    
    def get_current_dict(self):
        """
        Returns the dict for current weather
        @return dict{String,String}
        """
        return self.__current_dict
    
    def clear_current_dict(self):
        """
        Resets the dict for current weather
        """
        self.__current_dict = {}
        
    def set_current(self,key,value):
        """
        Sets the value for the key in the dict for the current weather
        @param key String
        @param value String
        """
        self.__current_dict[key] = value
        
    def get_current(self,key):
        """
        Returns the value for the key from the dict for the current weather
        @param key String
        @return String
        """
        return self.__current_dict[key]
    
    def clear_forecasts(self):
        """
        Resets the forecast array
        """
        self.__forecasts = []
        
    def add_forecast(self, forecast):
        """
        Appends a forecast to the forecast array
        @param forecast WeatherForecast
        """
        self.__forecasts.append(forecast)
        
    def get_forecasts(self):
        """
        Returns an array of forecasts
        @return array(WeatherForecast)
        """
        return self.__forecasts
    
    def refresh(self,xml=""):
        """
        Clear current weather and forecasts and then loads new data from xml or rss feed
        @param xml minidom_xml_doc If this is set the method will load data from the xml file, otherwise it will load from the rss feed xml
        """
        self.clear_forecasts()
        self.clear_current_dict()
        
        if xml == "":
            xmldoc = minidom.parse(urllib.urlopen(self.get_rss_url()))
        else:
            xmldoc = xml
        
        theNodes = []
        
        #Join the relevant nodes
        for node in xmldoc.getElementsByTagName('item')[0].childNodes:
            theNodes.append(node)
        for node in xmldoc.getElementsByTagName('channel')[0].childNodes:
            theNodes.append(node)
            
        #Loop through and add any yweather items to the dict for current weather
        for node in theNodes:
            name = node.nodeName.split(":")
            if (name[0] == "yweather") and (name[1] <> "forecast"):
                for attr in node.attributes.keys():
                    self.set_current(name[1]+"."+node.attributes[attr].name,node.attributes[attr].value)
                    
        try:
            self.set_type(self.get_current("units.temperature"))
        except KeyError:
            self.set_type("f")
                   
        #Add Forecasts
        for node in xmldoc.getElementsByTagName('yweather:forecast'):
            #day of the week
            day = node.getAttribute('day')
            #date of the forecast
            date = node.getAttribute('date')
            #low temperature cast to integer
            low = int(node.getAttribute('low'))
            #high temperature cast to integer
            high = int(node.getAttribute('high'))
            #description of weather
            text = node.getAttribute('text')
            #weather code cast to integer
            code = int(node.getAttribute('code'))
            
            self.add_forecast(WeatherForecast(day,date,low,high,text,code,self.get_current("units.temperature")))
        
class WeatherForecast:
    def __init__(self, day, date, low, high, text, code, tempType):
        """
        Sets up the WeatherForecast
        @param day String
        @param date String
        @param low Integer
        @param high Integer
        @param text String
        @param code Integer
        @param tempType String
        """
        self.day = day
        self.date = date
        self.low = low
        self.high = high
        self.text = text
        self.code = code
        self.type = tempType
        
    def to_string(self):
        """
        Returns a string representation of the WeatherForecast
        @return String
        """
        return self.day + " " + self.date + ", " + str(self.low) + self.type + ":" + str(self.high) + self.type + ", " + self.text + " (" + str(self.code) + ")"
    
